package com.chianghao.rockframewrok.starter.web;

import com.chianghao.rockframewrok.core.exception.HaoException;
import com.chianghao.rockframewrok.core.web.R;
import com.chianghao.rockframewrok.starter.web.properties.CorsProperties;
import com.chianghao.rockframewrok.starter.web.properties.SpringMvcProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

@Configuration
@EnableConfigurationProperties({SpringMvcProperties.class,CorsProperties.class})
@ControllerAdvice
public class SpringMvcAutoConfiguration implements WebMvcConfigurer, ResponseBodyAdvice<Object> {


    private static Logger logger = LoggerFactory.getLogger(SpringMvcAutoConfiguration.class);

	@Autowired
    private SpringMvcProperties springMvcProperties;

    @Autowired
    private CorsProperties corsProperties;

    
    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver factory = new CommonsMultipartResolver();
        factory.setMaxUploadSize(springMvcProperties.getMaxUploadSize());
        return factory;
    }

    @Bean
    @ConditionalOnProperty(value = {"chianghao.cors.isCors"}, matchIfMissing = false)
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin(corsProperties.getAllowedOrigin());
        config.addAllowedHeader(corsProperties.getAllowedHeader());
        config.addAllowedMethod(corsProperties.getAllowedMethod());
        source.registerCorsConfiguration(corsProperties.getPathExpression(), config); // CORS 配置对所有接口都有效
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        simpleModule.addSerializer(long.class, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        return objectMapper;
    }

    @Bean
    public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(){

            StringHttpMessageConverter shmc = new StringHttpMessageConverter();

            @Override
            public boolean canWrite(Class<?> clazz, MediaType mediaType) {
                boolean canWrite = super.canWrite(clazz, mediaType);
                if (!canWrite) {
                    canWrite = clazz.isAssignableFrom(String.class);
                }
                return canWrite;
            }

            @Override
            protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage)
                    throws IOException, HttpMessageNotWritableException {
                if (object != null && object instanceof String) {
                    outputMessage.getHeaders().setContentType(MediaType.TEXT_PLAIN);;
                    shmc.write((String)object, MediaType.TEXT_PLAIN, outputMessage);
                    return;
                }
                super.writeInternal(object, object != null ? object.getClass() : null, outputMessage);
            }

        };
        jackson2HttpMessageConverter.setObjectMapper(objectMapper());
        return jackson2HttpMessageConverter;
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Iterator<HttpMessageConverter<?>> it = converters.iterator();
        while (it.hasNext()) {
            HttpMessageConverter<?> messageConverter = it.next();
            if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
                it.remove();
            }
        }
        converters.add(0,jackson2HttpMessageConverter());
    }


	@Override
	public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}

	@Override
	public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
			Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
			ServerHttpResponse response) {

		if (body instanceof R){
            return body;
        }
		return R.data(body);
	}

    /***
     * 添加多语言拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                    throws Exception {
                String language = request.getHeader("language");
                request.setAttribute("sessionLanguage",language);
                return true;
            }
        });
    }

    /**
     * 400 - Bad Request
     * 参数验证失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public R<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        logger.error("参数验证失败", e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        //String field = error.getField();
        String message = error.getDefaultMessage();
        return R.error(message);
    }

    /**
     * 400 - Bad Request
     * 参数绑定失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    @ResponseBody
    public R<?> handleBindException(BindException e) {
        logger.error("参数绑定失败", e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String message = error.getDefaultMessage();
        return R.error(message);
    }


    /**
     * 400 - Bad Request
     * 参数验证失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public R<?> handleServiceException(ConstraintViolationException e) {
        logger.error("参数验证失败", e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        StringBuilder sb = new StringBuilder();
        while(violations.iterator().hasNext()) {
            ConstraintViolation<?> violation = violations.iterator().next();
            String message = violation.getMessage();
            sb.append(message);
        }
        return R.error(sb.toString());
    }

    /**
     * 自定义HaoException异常
     * @param e
     * @return
     */
    @ExceptionHandler(value = HaoException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public R<?> haoExceptionErrorHandler(HaoException e){
        logger.error("Exception", e);
        String message = e.getMessage();
        return R.error(e.getCode(),message);
    }

    /**
     * 400 - Bad Request
     * 参数验证失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    @ResponseBody
    public R<?> handleValidationException(ValidationException e) {
        logger.error("参数验证失败", e);
        return R.error("validation.error");
    }


    /**
     * 400 - Bad Request
     * 缺少请求参数
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public R<?> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        logger.error("缺少请求参数", e);
        return R.error("missing.servlet.request.parameter");
    }


    /**
     * 400 - Bad Request
     * 参数解析失败
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseBody
    public R<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        logger.error("参数解析失败", e);
        return R.error("http.not.readable");
    }

    /**
     * 404 - Not Found
     * 没有找到处理的handler
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseBody
    public R<?> noHandlerFoundException(NoHandlerFoundException e) {
        logger.error("Not Found", e);
        return R.error("no.handler.found.handler");
    }


    /**
     * 405 - Method Not Allowed
     * 处理方法不允许访问
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public R<?> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        return R.error("method.not.allowed");
    }

    /**
     * 415 - Unsupported Media Type
     * 不支持的类型
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseBody
    public R<?> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
        logger.error("不支持的媒体类型",e);
        return R.error("http.mediatype.not.supported");

    }
    /**
     * 其他异常 500
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public R<?> defaultErrorHandler(Exception e){
        logger.error("Exception", e);
        return R.error("server.error");
    }

}
